package nconv.convert.generator;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nconv.convert.ConvertUtils;
import nconv.convert.DocUtils;
import nconv.convert.BaseWordConverter;
import nconv.convert.TGPPRefProcessor;
import org.apache.poi.hwpf.HWPFDocumentCore;
import org.apache.poi.hwpf.model.StyleDescription;
import org.apache.poi.hwpf.model.StyleSheet;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.w3c.dom.Element;


public class TGPPToCGenerator {
	public static final String SPEC_OPEN_MARK_BEGIN = "<<";
	public static final String SPEC_OPEN_MARK_END = ">>";
	public static final String SPEC_CLOSE_MARK_BEGIN = "<<EOTOC";
	public static final String SPEC_CLOSE_MARK_END = ">>";
	public static final String PAGE_CLAUSE_MARK = "<##>";
	public static final String INVALID_CLAUSE_MARK = "<>";
	private static File tocSummary = new File("output/toc-tmp.txt");
	private static boolean refresh = true;
	
	public static void process(String specFileName) {
		try {
			File inFile = new File("input/" + specFileName);
			
			if (!inFile.exists()) {
				throw new IOException("File doesn't exist: " + specFileName);
			}
			
			String fileName = inFile.getName();
			if (fileName.indexOf('-') <= 0) {
				throw new IOException("Bad 3GPP spec file name: " + fileName);
			}
			String mark = fileName.substring(0, fileName.indexOf("-"));
			String openMark = SPEC_OPEN_MARK_BEGIN + mark + SPEC_OPEN_MARK_END;
			String closeMark = SPEC_CLOSE_MARK_BEGIN + mark + SPEC_CLOSE_MARK_END;
			
			try (BufferedReader br = new BufferedReader(new FileReader(tocSummary))) {
				StringBuffer sb = new StringBuffer();
				String line = null;
				while ((line = br.readLine()) != null) {
					sb.append(line);
				}
				String content = sb.toString();
				if (content.indexOf(openMark) >= 0) {
					System.out.println("ToC <" + mark + "> already defined in target file.");
					return;
				}
			}		
			
			try (BufferedWriter bw = new BufferedWriter(new FileWriter(tocSummary, true))) {
				bw.append(openMark + "\n");
				bw.append(ConvertUtils.processRetStr(specFileName, new TocConverter()));
				bw.append(closeMark + "\n");
			}
		} catch (Exception e) {
			System.err.println("Failed to generate ToC information for file: " + specFileName + ", error: " + e.getMessage());
		}
	}
	
	static Pattern annextToCPattern = Pattern.compile("[Aa]nnex\\s+([A-Z])\\s+.*");
	private static class TocConverter extends BaseWordConverter {

		@Override
		protected void processParagraph(HWPFDocumentCore wordDocument, Element parentElement, int currentTableLevel,
		    Paragraph paragraph, String bulletText) {
			StyleSheet styleSheet = wordDocument.getStyleSheet();
	    StyleDescription paragraphStyle = styleSheet.getStyleDescription(paragraph.getStyleIndex());
			String styleName = paragraphStyle.getName();
			if (!styleName.startsWith("TOC")) {
				return;
			}
			
			String text = paragraph.text().trim().replaceAll("IP\u001eCAN", "IP‑CAN")
							.replaceAll("IP‑CAN", "IP-CAN")
							.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "").replace((char)0x00a0, ' ');
			if (text.length() == 0) return;

			if (text.contains("\t")) {
				String[] fields = text.split("\\t");
				String clause = fields[0].trim();
				String title = fields[1].trim();
				Matcher m = annextToCPattern.matcher(clause);
				if (m.find()) {
					clause = m.group(1);
					//System.out.println(clause + "\t" + title);
				}
				clause = TGPPRefProcessor.validateClause(clause);
				text = clause + "\t" + title + "\n";
			}
			
			parentElement.appendChild(textDocumentFacade.createText(text));
		}
		
	}
	
	private static enum ToCParseState {
		OPEN,
		SPEC_READY,
		SPEC_CLOSED,
		PAGE_READY,
		PAGE_CLOSED
	};
	
	private static enum ToCParseEvent {
		SPEC_BEGIN,
		SPEC_END,
		NO_PAGE,
		PAGE_BEGIN,
		PAGE_CLOSE,
		TOC
	};
	
	private static ToCParseState state = ToCParseState.OPEN;
	static StringBuffer sb;
	static String pageName = null;
	static String pageFirstClause = null;
	static int pageStartIdx = 0;
	private static void process(ToCParseEvent event, String line, String content) {
		switch (state) {
		case OPEN:
		case SPEC_CLOSED:
			switch (event) {
			case SPEC_BEGIN:
				state = ToCParseState.SPEC_READY;
			}
			break;
		case SPEC_READY:
			switch (event) {
			case PAGE_BEGIN:
				pageName = content;
				pageStartIdx = sb.length();
				pageFirstClause = null;
				state = ToCParseState.PAGE_READY;
				break;
			case SPEC_END: //an empty spec
				state = ToCParseState.SPEC_CLOSED;
				break;
			case NO_PAGE: // do nothing
				break;
			case TOC: // do nothing
				break;
			}
			break;
		case PAGE_READY:
			switch (event) {
			case SPEC_END:
				process(ToCParseEvent.PAGE_CLOSE, line, pageName);
				process(ToCParseEvent.SPEC_END, line, content);
				break;
			case NO_PAGE:
				process(ToCParseEvent.PAGE_CLOSE, line, content);
				break;
			case PAGE_BEGIN:
				process(ToCParseEvent.PAGE_CLOSE, line, pageName);
				process(ToCParseEvent.PAGE_BEGIN, line, content);
				break;
			case PAGE_CLOSE:
				if (pageFirstClause != null && !pageFirstClause.equals(INVALID_CLAUSE_MARK)) {
					sb.insert(pageStartIdx, PAGE_CLAUSE_MARK);
				}
				state = ToCParseState.PAGE_CLOSED;
				break;
			case TOC:
				String curr = content.split("\\t")[0];
				if (pageFirstClause == null) {
					pageFirstClause = curr;
				} else {
					if (!curr.startsWith(pageFirstClause + ".")) {
						pageFirstClause = INVALID_CLAUSE_MARK;
					}
				}
				break;			
			}
			break;
		case PAGE_CLOSED:
			switch (event) {
			case PAGE_BEGIN:
				pageName = content;
				pageStartIdx = sb.length();
				state = ToCParseState.PAGE_READY;
				pageFirstClause = null;
				break;
			case SPEC_END:
				state = ToCParseState.SPEC_CLOSED;
				break;
			case NO_PAGE:
				break;
			case TOC:
				break;
			}
			break;
		
		}
	}
	
	public static void postProcess() throws FileNotFoundException, IOException {
		
		sb = new StringBuffer();
		try (BufferedReader br = new BufferedReader(new FileReader(tocSummary))) {
			String line = null;
			String content = null;
			ToCParseEvent event = null;
			while ((line = br.readLine()) != null) {
				if (line.startsWith(SPEC_OPEN_MARK_BEGIN) && line.endsWith(SPEC_OPEN_MARK_END)) {
					event = ToCParseEvent.SPEC_BEGIN;
					content = line.substring(SPEC_OPEN_MARK_BEGIN.length(), line.indexOf(SPEC_OPEN_MARK_END));
				} else if (line.startsWith(SPEC_CLOSE_MARK_BEGIN) && line.endsWith(SPEC_CLOSE_MARK_END)) {
					event = ToCParseEvent.SPEC_END;
					content = line.substring(SPEC_CLOSE_MARK_BEGIN.length(), line.indexOf(SPEC_CLOSE_MARK_END));
				} else if (line.startsWith(TGPPRefProcessor.PAGE_MARK_BEGIN) && line.endsWith(TGPPRefProcessor.PAGE_MARK_END)) {
					event = ToCParseEvent.PAGE_BEGIN;
					content = line.substring(TGPPRefProcessor.PAGE_MARK_BEGIN.length(), line.indexOf(TGPPRefProcessor.PAGE_MARK_END));
				} else if (line.equals(TGPPRefProcessor.NO_PAGE_MARK)) {
					event = ToCParseEvent.NO_PAGE;
				} else {
					event = ToCParseEvent.TOC;
					content = line;
				}
				sb.append(line + "\n");
				process(event, line, content);
			}
		}
		
		try (BufferedWriter bw = new BufferedWriter(new FileWriter(tocSummary))) {
			bw.append(sb.toString());
		}
	}
	
	public static void main(String[] args) throws FileNotFoundException, IOException {
		if (tocSummary.exists() && refresh) {
			tocSummary.delete();
		}
		if (!tocSummary.exists()) {
			tocSummary.createNewFile();
		}

		DocUtils.setSpecFileNames(DocUtils.SPEC_NAME_ARRAY);
		for (String spec : DocUtils.SPEC_NAME_ARRAY) {
			String fileName = DocUtils.getSpecDocFileName(spec);
			if (fileName == null) {
				System.err.println("Failed to locate a word file for spec: " + spec);
			} else {
				process(fileName);
			}
		}
		postProcess();
	}
}
